// 游戏房间匹配管理模块

#ifndef __MATCH_HPP__
#define __MATCH_HPP__
#include "Log.hpp"
#include "util.hpp"
#include "db.hpp"
#include "online_user.hpp"
#include "room.hpp"

#include <list>

/*匹配队列(阻塞队列), 用链表实现 , 这里不用 queue 是因为有 : 出队列中随机数据的需求*/
template <class T>
class MatchQueue
{
public:
    MatchQueue() = default;
    ~MatchQueue() = default;

    /*入队列数据 , 并唤醒线程*/
    void Push(const T &data)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        /*1. 链表(队列) 中插入数据 -- 生产者*/
        _match_queue.emplace_back(data);
        /*2. 唤醒任意线程*/
        WakeUpAll();
    }
    /*出队列队头数据*/
    bool Pop(T &data)
    {
        if (IsEmpty())
            return false;

        std::unique_lock<std::mutex> lock(_mutex);
        data = _match_queue.front();/*将数据带出去 , data 就是出队的数据*/
        _match_queue.pop_front();
        return true;
    }
    /*出队列中任意数据*/
    void RemoveData(const T &data)
    {
        if (IsEmpty())
            return;

        std::unique_lock<std::mutex> lock(_mutex);
        _match_queue.remove(data);
    }
    /*阻塞线程接口 , 如果没有满足等待条件 , 就阻塞等待*/
    void Wait()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        /*要在锁下等待 !*/
        _cond.wait(lock);
    }

    /*获取队列数据个数*/
    size_t GetQueuSize()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        return _match_queue.size();
    }
    /*判断队列为空*/
    bool IsEmpty()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        return _match_queue.empty();
    }
    /*唤醒所有线程*/
    void WakeUpAll()
    {
        _cond.notify_all();
    }

private:
    std::list<T> _match_queue;     /*匹配队列*/
    std::mutex _mutex;             /*保证线程安全 ,STL容器不是线程安全的*/
    std::condition_variable _cond; /*条件变量 , 因为队列是阻塞队列 , 阻塞消费者 , 如果队列中数据 >=2(有两个玩家了)在出数据*/
};

/*正式的匹配 , 管理匹配队列*/
// 匹配队列要完成的功能 :
/*1. 将玩家加入匹配队列 , 根据天梯分数实力匹配 ,匹配成功返回响应信息告诉玩家匹配成功*/
/*2. 将玩家从匹配队列中移除*/
/*所以 , 需要多个匹配队列 , 区分天梯等级 , 需要多个线程分别匹配 , 所以需要多个线程的入口函数*/
class MatchManage
{
private:
    /*总的线程处理函数 , 因为不同的处理动作都是一样的 , 只是条件不同 , 根据不断段位进行不同队列匹配*/
    void headler_match(MatchQueue<uint64_t> &que)
    {

        while (1)
        {
            /*1. 判断队列中是否 >=2 , 否则就阻塞等待 , 条件变量必须循环*/
            while (que.GetQueuSize() < 2)
            {
                /*阻塞等待 -- 在该条件变量(que.GetQueuSize() < 2)下等待*/
                que.Wait();
            }
            /*2. >=2 , 出队两个玩家*/
            uint64_t uid1, uid2;
            bool user1 = que.Pop(uid1);
            if (user1 == false)
            {
                WINGLOG("user1 pop failed !");
                continue;
            }
            bool user2 = que.Pop(uid2);
            if (user2 == false)
            {
                /*重新添加玩家1到匹配队列准备下次匹配*/
                this->AddMatch(uid1);
                WINGLOG("user2 pop failed !");
                continue;
            }
            /*3. 判断玩家是否在线大厅(有无连接) , 如有掉线者就不添加了*/
            wsserver_t::connection_ptr conn1 = _onlie_manage->GetConnectionFromHall(uid1);
            if (conn1.get() == nullptr)
            {
                /*玩家1掉线 , 将玩家2重新加入匹配队列匹配其他人*/
                this->AddMatch(uid2);
                continue;
            }
            wsserver_t::connection_ptr conn2 = _onlie_manage->GetConnectionFromHall(uid2);
            if (conn2.get() == nullptr)
            {
                /*玩家2掉线 , 将玩家1重新加入匹配队列匹配其他人*/
                this->AddMatch(uid1);
                continue;
            }
            /*4. 以上 , 玩家均在线并且也匹配成功了 , 所以为两个玩家创建游戏房间*/
            room_ptr rp = _room_manage->CreateRoom(uid1, uid2);
            if (rp.get() == nullptr)
            {
                /*创建房间失败 , 将两个玩家重新加入匹配队列重新匹配*/
                this->AddMatch(uid1);
                this->AddMatch(uid2);
                continue;
            }
            /*5. 进入房间后页面切换 , 大厅页面关闭 , 退出大厅 . 关闭大厅长连接 , 玩家准备进入房间*/
            // 主动让两个用户退出大厅
            _onlie_manage->ExitGameHall(uid1);
            _onlie_manage->ExitGameHall(uid2);
            /*6. 通知双方 , 匹配成功 !*/
            Json::Value json_resp;
            json_resp["optype"] = "match_success";
            json_resp["result"] = true;
            std::string body;
            json_util::Serialize(json_resp, body);
            /*websocket 通信 , 用 send*/
            conn1->send(body);
            conn2->send(body);
        }
    }

    // 提供不同的线程入口函数

    /*青铜线程入口函数*/
    void thread_enter_bronze() { return headler_match(_bronze); }
    /*白银线程入口函数*/
    void thread_enter_silver() { return headler_match(_silver); }
    /*黄金线程入口函数*/
    void thread_enter_gold() { return headler_match(_gold); }
    /*钻石线程入口函数*/
    void thread_enter_diamond() { return headler_match(_diamond); }
    /*王者线程入口函数*/
    void thread_enter_king() { return headler_match(_king); }

public:
    MatchManage(user_table *_utb, RoomManage *_rom, OnlineUserManage *_onm)
        : _user_table(_utb),
          _room_manage(_rom),
          _onlie_manage(_onm),
          /*设置线程入口函数*/
          _bronze_th(std::thread(&MatchManage::thread_enter_bronze, this)),
          _silver_th(std::thread(&MatchManage::thread_enter_silver, this)),
          _gold_th(std::thread(&MatchManage::thread_enter_gold, this)),
          _diamond_th(std::thread(&MatchManage::thread_enter_diamond, this)),
          _king_th(std::thread(&MatchManage::thread_enter_king, this))
    {
        DBGLOG("匹配管理器初始化成功 ! ");
    }

    /*根据不同段位 , 加入不同匹配*/
    bool AddMatch(const uint64_t uid)
    {
        Json::Value info;
        /*1 . 根据玩家ID , 获取玩家信息*/
        bool ret = _user_table->ObtainInfoByID(uid, info);
        if (ret == false)
        {
            DBGLOG("get user [ %lu ]score failed !", uid);
            return false;
        }
        /*2. 获取分数 , 并添加到相应段位的匹配队列中*/
        int score = info["score"].asInt();
        if (score < 2000)
        {
            /*添加到青铜匹配队列 , 里面已经加了锁*/
            _bronze.Push(uid);
        }
        else if (score >= 2000 && score < 3000)
        {
            /*添加到白银匹配队列 , 里面已经加了锁*/
            _silver.Push(uid);
        }
        else if (score >= 3000 && score < 4000)
        {
            /*添加到黄金匹配队列 , 里面已经加了锁*/
            _gold.Push(uid);
        }
        else if (score >= 4000 && score < 5000)
        {
            /*添加到钻石匹配队列 , 里面已经加了锁*/
            _diamond.Push(uid);
        }
        else
        {
            /*添加到王者匹配队列 , 里面已经加了锁*/
            _king.Push(uid);
        }
        return true;
    }
    /*移除匹配中的指定用户*/
    bool DeleteMatch(const uint64_t uid)
    {
        Json::Value info;
        /*1 . 根据玩家ID , 获取玩家信息*/
        bool ret = _user_table->ObtainInfoByID(uid, info);
        if (ret == false)
        {
            DBGLOG("get user [ %lu ]score failed !", uid);
            return false;
        }
        int score = info["score"].asInt();
        /*2. 根据玩家天梯分数从指定队列中移除*/
        if (score < 2000)
        {
            /*从青铜匹配队列中移除该用户 , 里面已经加了锁*/
            _bronze.RemoveData(uid);
        }
        else if (score >= 2000 && score < 3000)
        {
            /*从白银匹配队列中移除该用户 , 里面已经加了锁*/
            _silver.RemoveData(uid);
        }
        else if (score >= 3000 && score < 4000)
        {
            /*从黄金匹配队列中移除该用户 , 里面已经加了锁*/
            _gold.RemoveData(uid);
        }
        else if (score >= 4000 && score < 5000)
        {
            /*从钻石匹配队列中移除该用户 , 里面已经加了锁*/
            _diamond.RemoveData(uid);
        }
        else
        {
            /*从王者匹配队列中移除该用户 , 里面已经加了锁*/
            _king.RemoveData(uid);
        }
        return true;
    }

private:
    // 不同段位的匹配队列
    MatchQueue<uint64_t> _bronze;  /*青铜 : 天梯分数为 < 2000 , 队列中为玩家ID*/
    MatchQueue<uint64_t> _silver;  /*白银 : 天梯分数为 2000~3000*/
    MatchQueue<uint64_t> _gold;    /*黄金 : 天梯分数为 3000~4000 */
    MatchQueue<uint64_t> _diamond; /*钻石 : 天梯分数为 4000~5000 */
    MatchQueue<uint64_t> _king;    /*王者 : 天梯分数为 > 5000 */
    // 不同段位的线程
    std::thread _bronze_th;  /*青铜线程*/
    std::thread _silver_th;  /*白银线程*/
    std::thread _gold_th;    /*黄金线程*/
    std::thread _diamond_th; /*钻石线程*/
    std::thread _king_th;    /*王者线程*/
    // 访问玩家数据 , 需要用户表访问数据库
    user_table *_user_table;
    // 要对战匹配房间 , 需要房间管理模块
    RoomManage *_room_manage;
    // 匹配成功进入房间 , 需要在线用户管理模块
    OnlineUserManage *_onlie_manage;
};

#endif
/**
 * 该模块仅进行了语法测试 , 功能序结合后面业务测试
 *
 * 该模块的难点 :
 * 1 . headler_match 函数的处理 -- 重点关注 !
 */